`include "../include/cpu_defines.sv"
/*
    如果在传统的五级流水线中，TLB指令是在Mem阶段执行的
    访存的逻辑为 cpu产生vaddr -> tlb转换(VPN -> PFN)  + offset -> i/d_cache访存
    一个页的大小为4k，PFN有20位,offset有12位
    |----20-----||--12--|
        PFN       offset

    我们需要实现四条指令
    TLBP Probe TLB for Matching Entry
    TLBR Read Indexed TLB Entry
    TLBWI Write Indexed TLB Entry
    TLBWR Write Random TLB Entry
    注意在TLBWI 和 TLBWR 指令之后，仍在流水线的指令的地址就不再正确了，需要清空流水线
    在TLBR指令之后可能会改变CP0中的entryHI的ASID，需要清空流水线

	目前只支持4k页，
*/

module TLB(
	input logic clk,
	input logic resetn,
    input wire [31:0] inst_vaddr,
    input wire [31:0] rdata_vaddr,
    input wire [19: 0] vwtag,
    output logic [31:0] inst_paddr,
    output logic [31:0] rdata_paddr,
    output logic [19:0] pwtag,
	output logic iuncache,
	output logic druncache,
	output logic dwuncache,

    input logic TLBP,
    input logic TLBWI,
    input logic TLBWR,
    input logic TLBR,

    output wire [31:0] Index_out,
	output wire [31:0] EntryHi_out,
	output wire [31:0] PageMask_out,
	output wire [31:0] EntryLo0_out,
	output wire [31:0] EntryLo1_out,
    

    input wire  [31:0] Index_in,
    input  wire [31:0] EntryHi_in,
	input  wire [31:0] PageMask_in,
	input  wire [31:0] EntryLo0_in,
	input  wire [31:0] EntryLo1_in,
    input  wire [31:0] Random_in,
	input logic kseg0_uncache,
    //异常
    output logic inst_tlb_refill, inst_tlb_invalid,
	output logic rdata_tlb_refill, rdata_tlb_invalid,
    output logic wdata_tlb_refill, wdata_tlb_invalid, data_tlb_modify
);

typedef struct packed {
    reg [18:0] vpn2; // 虚拟tag
    reg [7:0] asid; // 线程编号
    reg G; // 所有线程共同使用

    reg [19:0] PFN0; // 虚拟地址第12位为0时物理tag
    reg [2:0] c0; // cache: 3'b010表示uncache， 3'b011表示cache， 其他位保留
    reg d0; // dirty
    reg v0; // valid

    reg [19:0] PFN1;
    reg [2:0] c1;
    reg d1;
    reg v1;
} tlb_item;

tlb_item tlb[`TLB_NUM - 1 : 0];

logic [`TLB_NUM -1 : 0] find_inst, find_rdata, find_wdata;
wire [`TAG_WIDTH - 2 : 0] inst_vpn, rdata_vpn, wdata_vpn;
wire inst_vpn_last_bit, rdata_vpn_last_bit, wdata_vpn_last_bit;
wire [`TLB_WIDTH] find_inst_encode, find_rdata_encode, find_wdata_encode;
wire [`TAG_WIDTH-1:0] inst_pfn;
wire [`TAG_WIDTH-1:0] rdata_pfn;
wire [`TAG_WIDTH-1:0] wdata_pfn; 
logic inst_unmapped, rdata_unmapped, wdata_unmapped;

logic [`TLB_NUM -1 : 0] tlbp_find_all;
wire tlbp_find;

`ifndef PERFORMENCE_TEST
wire [`TAG_WIDTH - 2 : 0] tlbp_vpn = EntryHi_in[31: 13];
wire [7: 0] tlbp_asid = EntryHi_in[7: 0];

assign inst_vpn = inst_vaddr[31: 13];
assign rdata_vpn = rdata_vaddr[31: 13];
assign wdata_vpn = vwtag[19: 1];
assign inst_vpn_last_bit = inst_vaddr[12];
assign rdata_vpn_last_bit = rdata_vaddr[12];
assign wdata_vpn_last_bit = vwtag[0];

//这里可能找到多个，挑一个出来
always_comb begin
    for (int i = 0; i < `TLB_NUM; i ++) begin
        //先不看asid
		find_inst[i] =      ((tlb[i].vpn2) == (inst_vpn)) && 
                            (tlb[i].G || tlb[i].asid == EntryHi_in[7:0]);
		find_rdata[i] =     ((tlb[i].vpn2) == (rdata_vpn)) && 
                            (tlb[i].G || tlb[i].asid == EntryHi_in[7:0]);
		find_wdata[i] =     ((tlb[i].vpn2) == (wdata_vpn))  && 
                            (tlb[i].G || tlb[i].asid == EntryHi_in[7:0]);
		tlbp_find_all[i] =  ((tlb[i].vpn2) == (tlbp_vpn)) && 
                            (tlb[i].G || tlb[i].asid == EntryHi_in[7:0]);
    end
end
assign tlbp_find = |tlbp_find_all;
logic  [`TLB_WIDTH] tlbp_find_index;
priority_encoder_32to5 encode_find_inst(
    .in(find_inst),
    .out(find_inst_encode)
);
priority_encoder_32to5 encode_find_rdata(
    .in(find_rdata),
    .out(find_rdata_encode)
);
priority_encoder_32to5 encode_find_wdata(
    .in(find_wdata),
    .out(find_wdata_encode)
);
priority_encoder_32to5 encode_find_tlbp(
    .in(tlbp_find_all),
    .out(tlbp_find_index)
);

//kseg1 和 kseg0 unmap
assign inst_unmapped = inst_vaddr[31] & ~inst_vaddr[30];
assign rdata_unmapped = rdata_vaddr[31] & ~rdata_vaddr[30];
assign wdata_unmapped = vwtag[19] & ~vwtag[18];
assign inst_pfn = inst_vpn_last_bit ? tlb[find_inst_encode].PFN1 : tlb[find_inst_encode].PFN0;
assign rdata_pfn = rdata_vpn_last_bit ? tlb[find_rdata_encode].PFN1 : tlb[find_rdata_encode].PFN0;
assign wdata_pfn = wdata_vpn_last_bit ? tlb[find_wdata_encode].PFN1 : tlb[find_wdata_encode].PFN0;

assign pwtag = (wdata_unmapped) ? {3'b0, vwtag[16:0]} : wdata_pfn;
assign dwuncache = vwtag[19] & ~vwtag[18] & (vwtag[17] | kseg0_uncache) | ~wdata_unmapped & (wdata_vpn_last_bit ? tlb[find_wdata_encode].c1 != 3'b011 : tlb[find_wdata_encode].c0 != 3'b011);
assign wdata_tlb_refill = ~(|find_wdata) & ~wdata_unmapped;
assign wdata_tlb_invalid = (|find_wdata) & ~(wdata_vpn_last_bit ? tlb[find_wdata_encode].v1 : tlb[find_wdata_encode].v0) & ~wdata_unmapped;
assign data_tlb_modify = (|find_wdata) & (wdata_vpn_last_bit ? tlb[find_wdata_encode].v1 : tlb[find_wdata_encode].v0) & ~(wdata_vpn_last_bit ? tlb[find_wdata_encode].d1 : tlb[find_wdata_encode].d0) & ~wdata_unmapped;
`endif 

`ifdef PERFORMENCE_TEST		
assign pwtag = vwtag[19] & ~vwtag[18] ? {3'b0, vwtag[16:0]} : vwtag;
assign dwuncache = vwtag[19] & ~vwtag[18] & vwtag[17];
assign wdata_tlb_invalid = 1'b0;
assign wdata_tlb_refill = 1'b0;
assign data_tlb_modify = 1'b0;
`endif

always_ff @(posedge clk)begin
	if(resetn == 1'b0)begin
		inst_paddr <= 0;
		rdata_paddr <= 0;
		iuncache <= 1'b0;
		druncache <= 1'b0;
		inst_tlb_refill <= 1'b0;
		inst_tlb_invalid <= 1'b0;
		rdata_tlb_refill <= 1'b0;
		rdata_tlb_invalid <= 1'b0;
	end
	else begin
`ifdef PERFORMENCE_TEST
		inst_paddr <= inst_vaddr[31] & ~inst_vaddr[30] ? {3'b0, inst_vaddr[28: 0]} : inst_vaddr;
		rdata_paddr <= rdata_vaddr[31] & ~rdata_vaddr[30] ? {3'b0, rdata_vaddr[28: 0]} : rdata_vaddr;
		iuncache <= 1'b0;
		druncache <= rdata_vaddr[31] & ~rdata_vaddr[30] & rdata_vaddr[29];
		inst_tlb_invalid <= 1'b0;
		inst_tlb_invalid <= 1'b0;
		rdata_tlb_refill <= 1'b0;
		rdata_tlb_invalid <= 1'b0;
`else
		inst_paddr <= (inst_unmapped) ? {3'b0, inst_vaddr[28:0]} : {inst_pfn, inst_vaddr[11:0]};
		rdata_paddr <= (rdata_unmapped) ? {3'b0, rdata_vaddr[28:0]} : {rdata_pfn, rdata_vaddr[11:0]};
		iuncache <= inst_vaddr[31] & ~inst_vaddr[30] & inst_vaddr[29] | ~inst_unmapped & (inst_vpn_last_bit ? tlb[find_inst_encode].c1 != 3'b011 : tlb[find_inst_encode].c0 != 3'b011);
		druncache <= rdata_vaddr[31] & ~rdata_vaddr[30] & (rdata_vaddr[29] | kseg0_uncache) | ~rdata_unmapped & (rdata_vpn_last_bit ? tlb[find_rdata_encode].c1 != 3'b011 : tlb[find_rdata_encode].c0 != 3'b011);
		inst_tlb_refill <= ~(|find_inst) & ~inst_unmapped;
		inst_tlb_invalid <= (|find_inst) & ~(inst_vpn_last_bit ? tlb[find_inst_encode].v1 : tlb[find_inst_encode].v0) & ~inst_unmapped;
		rdata_tlb_refill <= ~(|find_rdata) & ~rdata_unmapped;
		rdata_tlb_invalid <= (|find_rdata) & ~(rdata_vpn_last_bit ? tlb[find_rdata_encode].v1 : tlb[find_rdata_encode].v0) & ~rdata_unmapped;

		// pwtag <= (wdata_unmapped) ? {3'b0, vwtag[16:0]} : wdata_pfn;
		// dwuncache <= vwtag[19] & ~vwtag[18] & (vwtag[17] | kseg0_uncache) | ~wdata_unmapped & (wdata_vpn_last_bit ? tlb[find_wdata_encode].c1 : tlb[find_wdata_encode].c0);
		// wdata_tlb_refill <= ~(|find_wdata) & ~wdata_unmapped;
		// wdata_tlb_invalid <= (|find_wdata) & ~(wdata_vpn_last_bit ? tlb[find_wdata_encode].v1 : tlb[find_wdata_encode].v0) & ~wdata_unmapped;
		// data_tlb_modify <= (|find_wdata) & (wdata_vpn_last_bit ? tlb[find_wdata_encode].v1 : tlb[find_wdata_encode].v0) & ~(wdata_vpn_last_bit ? tlb[find_wdata_encode].d1 : tlb[find_wdata_encode].d0) & ~wdata_unmapped;
`endif
	end
end

`ifdef PERFORMENCE_TEST
	assign Index_out = 0;
	assign EntryHi_out = 0;
	assign EntryLo0_out = 0;
	assign EntryLo1_out = 0;
	assign PageMask_out = 0;
`endif

`ifndef PERFORMENCE_TEST
logic [`TLB_WIDTH] read_index;
assign read_index = Index_in[`TLB_WIDTH];

assign Index_out    = {~tlbp_find, 26'b0, tlbp_find_index};
assign EntryHi_out  = {tlb[read_index].vpn2, 5'b0 , tlb[read_index].asid};
assign EntryLo0_out = {6'b0, tlb[read_index].PFN0, tlb[read_index].c0, tlb[read_index].d0, tlb[read_index].v0, tlb[read_index].G};
assign EntryLo1_out = {6'b0, tlb[read_index].PFN1, tlb[read_index].c1, tlb[read_index].d1, tlb[read_index].v1, tlb[read_index].G};
assign PageMask_out = 0;

logic [`TLB_WIDTH] write_index;
// assign write_index = TLBWI ? read_index[`TLB_WIDTH] : Random_in[`TLB_WIDTH];
logic [31: 0] entryhi_next, entrylo0_next, entrylo1_next;
logic tlb_write;
always @(posedge clk)
begin
	entryhi_next <= EntryHi_in;
	entrylo0_next <= EntryLo0_in;
	entrylo1_next <= EntryLo1_in;
	tlb_write <= TLBWI | TLBWR;
	write_index <= TLBWI ? read_index[`TLB_WIDTH] : Random_in[`TLB_WIDTH];
    if(resetn == 1'b0) begin
        for (int i = 0; i < `TLB_NUM; i ++) begin
            tlb[i] <= 0;
        end
    end
    else begin
        if(tlb_write) begin
            tlb[write_index].vpn2 <= entryhi_next[31:13];
            tlb[write_index].asid <= entryhi_next[7:0];
            tlb[write_index].G <= entrylo0_next[0] & entrylo1_next[0];

            tlb[write_index].PFN0 <= entrylo0_next[25:6];
            tlb[write_index].c0 <= entrylo0_next[5:3];
            tlb[write_index].d0 <= entrylo0_next[2];
            tlb[write_index].v0 <= entrylo0_next[1];

            tlb[write_index].PFN1 <= entrylo1_next[25:6];
            tlb[write_index].c1 <= entrylo1_next[5:3];
            tlb[write_index].d1 <= entrylo1_next[2];
            tlb[write_index].v1 <= entrylo1_next[1];
        end
    end
end
`endif
endmodule

module priority_selector32_encode(
	input logic [31: 0] in,
	output logic [4: 0] out
);
integer i = 0;
assign out =    in[31] ? 31:
                in[30] ? 30:
                in[29] ? 29:
                in[28] ? 28:
                in[27] ? 27:
                in[26] ? 26:
                in[25] ? 25:
                in[24] ? 24:
                in[23] ? 23:
                in[22] ? 22:
                in[21] ? 21:
                in[20] ? 20:
                in[19] ? 19:
                in[18] ? 18:
                in[17] ? 17:
                in[16] ? 16:
                in[15] ? 15:
                in[14] ? 14:
                in[13] ? 13:
                in[12] ? 12:
                in[11] ? 11:
                in[10] ? 10:
                in[9] ? 9:
                in[8] ? 8:
                in[7] ? 7:
                in[6] ? 6:
                in[5] ? 5:
                in[4] ? 4:
                in[3] ? 3:
                in[2] ? 2:
                in[1] ? 1 : 0;
endmodule
